#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "gkt_board.h"
#include "gkt_debug.h"
#include "gkt_flash_partition.h"

#define FLASH_PARTITION_RDONLY		0
#define FLASH_PARTITION_WRITABLE	1

typedef struct _flash_device {
	const char	*file_name;
	uint32_t	max_size;
	uint32_t	sector_size;
	int	map_read_enable;

	FILE	*fp;
	void	*cache_buffer;
	uint32_t	next_partition_offset;
} flash_device_s;
static flash_device_s s_flash_device_table[] = {
	{ "eflash.dat", GKT_EFLASH_MAIN_SIZE, GKT_EFLASH_MAIN_SECTOR_SIZE, 1, NULL, NULL, 0 },
	{ "spiflash.dat", GKT_SPIFLASH_MAXSIZE, GKT_SPIFLASH_SECTOR_SIZE, 0, NULL, NULL, 0 },
};
static const uint32_t sc_flash_device_table_size =
	sizeof(s_flash_device_table) / sizeof(flash_device_s);

typedef struct _flash_partition {
	const char	*name;
	uint32_t	device_id;	/* device id */
	uint32_t	offset;	/* offset in device */
	int	writable;
	gkt_flash_partition_info_s	info;
} flash_partition_s;

#define GKT_FLASH_PARTITION(part_name, device_name, part_offset, part_size, rw_perm)	\
	{	\
		.name	= part_name,	\
		.device_id	= GKT_##device_name##_ID,	\
		.offset	= part_offset,	\
		.writable	= FLASH_PARTITION_##rw_perm,	\
		.info	= {	\
			.id	= -1,	\
			.size	= (uint32_t)(part_size),	\
			.read_map_addr	= NULL,	\
		},	\
	}
static flash_partition_s s_flash_partition_table[] =
	{ GKT_CONFIG_FLASH_PARTITION_TABLE };
static const uint32_t sc_flash_partition_table_size =
	sizeof(s_flash_partition_table) / sizeof(flash_partition_s);

int gkt_flash_partition_init(void)
{
	flash_device_s *device;
	flash_partition_s *partition;
	uint32_t aligned_offset, aligned_size;
	uint32_t i, size;

	for (i = 0; i < sc_flash_device_table_size; i++) {
		device = &s_flash_device_table[i];
		gkt_info("flash_partition_init: device(%s), size(0x%x - 0x%x)\n",
					device->file_name, device->sector_size, device->max_size);

		device->fp = fopen(device->file_name, "w+");
		if (device->fp) {
			device->cache_buffer = gkt_malloc(device->max_size);
			if (device->cache_buffer) {
				size = (uint32_t)fread(device->cache_buffer, 1, device->max_size, device->fp);
				if (size == device->max_size)
					continue;

				gkt_error("flash_partition_init: read cache_buffer failed!\n");
				memset(device->cache_buffer, 0xff, device->max_size);
			}
			else {
				gkt_error("flash_partition_init: malloc cache_buffer failed!\n");
			}
		}
		else {
			gkt_error("flash_partition_init: open file failed!\n");
		}

		return GKT_ELOWLEVEL;
	}

	for (i = 0; i < sc_flash_partition_table_size; i++) {
		partition = &s_flash_partition_table[i];
		if (partition->device_id < sc_flash_device_table_size) {
			device = &s_flash_device_table[partition->device_id];
			if (partition->offset)
				aligned_offset = gkt_aligned(partition->offset, device->sector_size);
			else
				aligned_offset = device->next_partition_offset;
			aligned_size = gkt_aligned(partition->info.size, device->sector_size);
			if ((aligned_offset >= device->next_partition_offset)
				&& ((aligned_offset + aligned_size) <= device->max_size))
			{
				partition->offset = aligned_offset;
				partition->info.id = (int)i;
				partition->info.sector_size = device->sector_size;
				partition->info.size = aligned_size;
				if (device->map_read_enable)
					partition->info.read_map_addr = (const void *)((uint8_t *)device->cache_buffer + aligned_offset);
				else
					partition->info.read_map_addr = NULL;
				gkt_info("flash_partition_init: [%u] partition(%u)(%s) - (0x%x)(0x%x) - (%p)\n",
					i, partition->device_id, partition->name, 
					aligned_offset, aligned_size,
					partition->info.read_map_addr);

				device->next_partition_offset = aligned_offset + aligned_size;
				continue;
			}
		}

		gkt_error("flash_partition_init: [%u] partition invalid!\n", i);
		return GKT_EINVALIDCONFIG;
	}

	return GKT_SUCCESS;
}

const gkt_flash_partition_info_s
*gkt_flash_partition_get_info(const char *name)
{
	flash_partition_s *partition;
	uint32_t id;

	if (name) {
		for (id = 0; id < sc_flash_partition_table_size; id++) {
			partition = &s_flash_partition_table[id];
			if (!strcmp(name, partition->name))
				return (const gkt_flash_partition_info_s  *)&partition->info;
		}
	}

	return NULL;
}

int gkt_flash_partition_read(int id,
	uint32_t offset, uint32_t size,
	void *buffer)
{
	flash_device_s *device;
	flash_partition_s *partition;
	int retval = GKT_EPARAM;

	if ((id >= 0) && (id < (int)sc_flash_partition_table_size)
		&& buffer && (size > 0)) 
	{
		partition = &s_flash_partition_table[id];
		if ((offset < partition->info.size)
			&& (size <= partition->info.size)
			&& ((offset + size) <= partition->info.size))
		{
			device = &s_flash_device_table[partition->device_id];
			memcpy(buffer, (uint8_t *)device->cache_buffer + partition->offset + offset, size);
			return GKT_SUCCESS;
		}
	}

	return GKT_EPARAM;
}

int gkt_flash_partition_write(int id,
	uint32_t offset, uint32_t size,
	const void *data, int pre_erase)
{
	flash_device_s *device;
	flash_partition_s *partition;
	uint32_t size;
	int retval = GKT_EPARAM;

	if ((id >= 0) && (id < (int)sc_flash_partition_table_size)
		&& data && (size > 0)) 
	{
		partition = &s_flash_partition_table[id];
		if (partition->writable
			&& (offset < partition->info.size)
			&& (size <= partition->info.size)
			&& ((offset + size) <= partition->info.size))
		{
			device = &s_flash_device_table[partition->device_id];
			memcpy((uint8_t *)device->cache_buffer + partition->offset + offset, data, size);
			size = (uint32_t)fwrite(device->cache_buffer, 1, device->max_size, device->fp);
			retval = (size == device->max_size) ? GKT_SUCCESS : GKT_ELOWLEVEL;
		}
	}

	return retval;
}

int gkt_flash_partition_erase(int id,
	uint32_t offset, uint32_t size)
{
	flash_device_s *device;
	flash_partition_s *partition;
	uint32_t size;
	int retval = GKT_EPARAM;

	if ((id >= 0) && (id < (int)sc_flash_partition_table_size)
		&& (size > 0)) 
	{
		partition = &s_flash_partition_table[id];
		if (partition->writable
			&& (offset < partition->info.size)
			&& (size <= partition->info.size)
			&& ((offset + size) <= partition->info.size))
		{
			device = &s_flash_device_table[partition->device_id];
			memset((uint8_t *)device->cache_buffer + partition->offset + offset, 0xff, size);
			size = (uint32_t)fwrite(device->cache_buffer, 1, device->max_size, device->fp);
			retval = (size == device->max_size) ? GKT_SUCCESS : GKT_ELOWLEVEL;
		}
	}

	return retval;
}

int gkt_flash_partition_erase_all(int id)
{
	flash_device_s *device;
	flash_partition_s *partition;
	uint32_t size;
	int retval = GKT_EPARAM;

	if ((id >= 0) 
		&& (id < (int)sc_flash_partition_table_size)) 
	{
		partition = &s_flash_partition_table[id];
		if (partition->writable) {
			device = &s_flash_device_table[partition->device_id];
			memset((uint8_t *)device->cache_buffer + partition->offset, 0xff, partition->info.size);
			size = (uint32_t)fwrite(device->cache_buffer, 1, device->max_size, device->fp);
			retval = (size == device->max_size) ? GKT_SUCCESS : GKT_ELOWLEVEL;
		}
	}

	return retval;
}

